1
2
3
4
5
6
7
8
9
10
11 package org.astrogrid.acr;
12
13 import net.ladypleaser.rmilite.Client;
14
15 import org.astrogrid.acr.builtin.ACR;
16 import org.astrogrid.acr.builtin.Shutdown;
17 import org.astrogrid.acr.builtin.ShutdownListener;
18 import org.astrogrid.acr.system.ApiHelp;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22
23
24 import java.awt.HeadlessException;
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.FileReader;
29 import java.io.IOException;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.net.MalformedURLException;
33 import java.net.URL;
34 import java.rmi.NotBoundException;
35 import java.rmi.RemoteException;
36
37 import javax.swing.JOptionPane;
38
39 /*** Find or create an ACR server, and return an interface to that service.
40 *
41 * first attempts to connect to a running instance using RMI, on a port defined in the file <tt>~/.acr-rmi-port</tt> (which is written by a running ACR instance<br />
42 * failing that, tries to create an external instance (will only work if running under java web start), and then connect to that using RMI<br />
43 * failing that, trys to create an instance internally (will only work if implementation classes are on classpath),<br />
44 * the interface returned will either be a rmi stub or direct instance, depending on how the ACR was found.
45 * <br />
46 * No matter how the acr is found, the ACR returned is a singleton - it is stored in this class for simple access the next time
47 * @author Noel Winstanley nw@jb.man.ac.uk 26-Jul-2005
48 * @example
49 * <pre>
50 * import org.astrogrid.acr.builtin.ACR;
51 * import org.astrogrid.acr.Finder;
52 * Finder f = new Finder();
53 * ACR acr = f.find();
54 * </pre>
55 *@see org.astrogrid.acr.builtin.ACR
56 */
57 public class Finder {
58 /*** Webstart URL for the ACR */
59 public static final String ACR_JNLP_URL = "http://software.astrogrid.org/jnlp/beta/workbench/workbench.jnlp";
60 /***
61 * Commons Logger for this class
62 */
63 private static final Log logger = LogFactory.getLog(Finder.class);
64
65 /*** Construct a new Finder
66 *
67 */
68 public Finder() {
69 super();
70 }
71
72 /*** Find or create a running ACR server.
73 *
74 * first attempts to connect to a running instance, on a port defined in the file <tt>~/.acr-rmi-port</tt> (which is written by a running ACR instance<br />
75 * failing that, tries to create an external instance (will only work if running under java web start) and then connect to that using RMI<br/>
76 * failing that, trys to create an instance internally (will only work if implementation classes are on classpath),<br/>
77 * @return an interface to the running ACR - depending on how connected will either be a direct instance or a remote stub - although this makes no difference to the consumer.
78 * The instance returned is a singleton - i.e. all subsequent calls to {@link #find} will return the same object.
79 * @throws ACRException if all options fail
80 *
81 * */
82 public synchronized ACR find() throws ACRException{
83 boolean tryToStartIfNotRunning = true;
84 boolean warnUserBeforeStarting = false;
85
86 return find(tryToStartIfNotRunning, warnUserBeforeStarting);
87
88 }
89
90 /***
91 * Find or create a running ACR server.
92 * @see #find()
93 * @param tryToStartIfNotRunning if false, will not attempt to start an ACR, but merely return NULL if there isn't one running
94 * @param warnUserBeforeStarting if true, will warn the user before attempting start an ACR, giving him the chance to start one manually
95 * @return
96 * @throws ACRException
97 */
98 public ACR find(boolean tryToStartIfNotRunning, boolean warnUserBeforeStarting) throws ACRException {
99 if (acr == null) {
100 acr= createACR(tryToStartIfNotRunning, warnUserBeforeStarting);
101 try {
102 Shutdown sd = (Shutdown)acr.getService(Shutdown.class);
103 sd.addShutdownListener(new ShutdownListener() {
104 public void halting() {
105 logger.info("Host ACR shutting down");
106 Finder.this.acr = null;
107 }
108 public String lastChance() {
109 return null;
110 }
111 });
112 } catch (ACRException e) {
113 logger.warn("Failed to register shutdown listener - no matter",e);
114 }
115
116 }
117 return acr;
118 }
119
120 /***
121 * @param userWarning If not null, prompt the user <b>before</b> attempting to start an external ACR.
122 * @param tryToStartIfNotRunning if false, don't start an external ACR if there isn't one.
123 * @throws NoAvailableACRException
124 */
125 private ACR createACR(boolean tryToStartIfNotRunning, boolean warnUser) throws ACRException {
126 logger.info("Searching for acr");
127 ACR result = null;
128 try {
129 result = connectExternal();
130 if (result != null) {
131 return result;
132 }
133 } catch (Exception e) {
134 logger.warn("Failed to connect to existing external acr",e);
135 }
136
137 try {
138 result = createInternal();
139 if (result != null) {
140 return result;
141 }
142 } catch (Exception e) {
143 logger.warn("Failed to create internal acr",e);
144 }
145
146 if (tryToStartIfNotRunning) {
147 try {
148 if (warnUser) {
149 try {
150
151 JOptionPane.showMessageDialog(null,"<html>This application requires the ACR.<br>Either start it yourself and press 'OK', or simply press 'OK' to auto-start it.</html>",
152 "ACR Required",JOptionPane.INFORMATION_MESSAGE);
153
154 result = connectExternal();
155 if (result != null) {
156 return result;
157 }
158 } catch (HeadlessException he) {
159 logger.warn("Not running in a ui environment - can't ask permission, so starting ACR anyway");
160 } catch (Exception e) {
161 logger.warn("Failed to connect to external acr.",e);
162 }
163 }
164 logger.info("Still no ACR, so attempt to create one");
165 createExternal();
166
167
168 long now = System.currentTimeMillis();
169 final int WAIT_TIME = (2 * 60 * 1000);
170 long tooLong = now + WAIT_TIME ;
171 while (connectExternal()==null) {
172 if (System.currentTimeMillis()>tooLong) {
173
174 int continueToWait;
175 try {
176 continueToWait = JOptionPane.showConfirmDialog(null,"<html>The ACR hasn't started yet. Press OK to continue waiting, Cancel to abort.</html>","ACR not started",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE);
177 } catch (HeadlessException e) {
178 logger.warn("Not running in a ui environment - can't prompt");
179 continueToWait = JOptionPane.CANCEL_OPTION;
180 }
181 if (continueToWait == JOptionPane.CANCEL_OPTION) {
182 break;
183 } else {
184 tooLong += WAIT_TIME;
185 }
186 }
187 Thread.sleep(5000);
188 }
189 result = connectExternal();
190 if (result != null) {
191 return result;
192 }
193 } catch (Exception e) {
194 logger.warn("Failed to create external acr",e);
195 }
196
197 int dialogueResult;
198 try {
199 dialogueResult = JOptionPane.showConfirmDialog(null,"<html><b>Please start the ACR by hand</b><br>When started press 'Ok'. To halt press 'Cancel'","Unable to automatically start ACR",JOptionPane.OK_CANCEL_OPTION,JOptionPane.WARNING_MESSAGE);
200 } catch (HeadlessException e) {
201 logger.warn("Not running in a ui environment - can't prompt");
202 dialogueResult = JOptionPane.NO_OPTION;
203 }
204
205 if (dialogueResult == JOptionPane.YES_OPTION) {
206 try {
207 result = connectExternal();
208 if (result != null) {
209 return result;
210 }
211 } catch (Exception e) {
212 logger.warn("Failed to connect to external acr, after user claimed to start one.",e);
213
214 }
215 }
216 }
217
218 throw new ACRException("Failed to find or create an ACR to connect to");
219 }
220
221 /*** connect to an external acr.
222 * @return
223 * @throws FileNotFoundException
224 * @throws NumberFormatException
225 * @throws IOException
226 * @throws RemoteException
227 * @throws NotBoundException
228 */
229 private ACR connectExternal() throws FileNotFoundException, NumberFormatException, IOException, RemoteException, NotBoundException {
230 File conf = configurationFile();
231 if (!conf.exists()) {
232 logger.info("No configuration file - suggests an acr instance is not running at the moment");
233 return null;
234 }
235
236 logger.info("configuration file indicates an acr is already running");
237 BufferedReader br = new BufferedReader(new FileReader(conf));
238 int port = Integer.parseInt(br.readLine());
239 br.close();
240 logger.info("Port determined to be " + port);
241 final Client client = new Client("localhost",port);
242 final ApiHelp api = (ApiHelp)client.lookup(ApiHelp.class);
243 ACR newAcr = new ACR() {
244
245 public Object getService(Class interfaceClass) throws ACRException, NotFoundException {
246 if (interfaceClass.equals(ACR.class)) {
247 return this;
248 }
249 try {
250 registerListeners(interfaceClass);
251 return client.lookup(interfaceClass);
252 } catch (RemoteException e) {
253 throw new ACRException(e);
254 } catch (NotBoundException e) {
255 throw new NotFoundException(e);
256 }
257 }
258
259 private void registerListeners(Class c) {
260 Method[] arr = c.getMethods();
261 for (int i = 0; i < arr.length; i++) {
262 Method m = arr[i];
263 Class[] ps = m.getParameterTypes();
264 for (int j = 0; j < ps.length; j++) {
265 maybeRegister(ps[j]);
266 }
267 Class ret = m.getReturnType();
268 maybeRegister(ret);
269 }
270 }
271 private void maybeRegister(Class c) {
272 if (c.isInterface() && c.getName().endsWith("Listener")) {
273 logger.debug("Exporting interface " + c.getName());
274 client.exportInterface(c);
275 }
276 }
277
278
279 public Object getService(String componentName) throws ACRException, NotFoundException {
280 String className = api.interfaceClassName(componentName);
281 Class clazz = null;
282 try {
283 clazz = Class.forName(componentName);
284 } catch (ClassNotFoundException e) {
285
286 try {
287 clazz = Class.forName(className);
288 } catch (ClassNotFoundException e1) {
289 throw new NotFoundException(e1);
290 }
291 }
292 return getService(clazz);
293 }
294 };
295
296
297 return newAcr;
298
299 }
300
301
302 private ACR acr;
303
304 public static final File configurationFile() {
305 File homeDir = new File(System.getProperty("user.home"));
306 return new File(homeDir,".acr-rmi-port");
307 }
308
309
310
311 /*** create an external instance of the acr
312 * would like to be able to do this by fetching the jnlp file - however, don't know that this process is
313 * running under javaws - and so need to find another library to control the system browser.
314 * blechh.
315 * for now, only create an external acr if running under javaws.
316 * @todo add browser control lib to do this in other circumstances.
317 * @todo think about using jnlp installer extensions to do this?
318 * @return
319 * @throws NoSuchMethodException
320 * @throws SecurityException
321 * @throws MalformedURLException
322 * @throws InvocationTargetException
323 * @throws IllegalAccessException
324 * @throws IllegalArgumentException
325 * @throws ClassNotFoundException
326 * @throws ClassNotFoundException
327 */
328 private void createExternal() throws SecurityException, NoSuchMethodException, MalformedURLException, IllegalArgumentException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
329 Method showMethod = null;
330 Object methodTarget = null;
331 try {
332 Class managerClass = Class.forName("javax.jnlp.ServiceManager");
333 Method lookupMethod= managerClass.getMethod("lookup",new Class[]{String.class});
334 methodTarget = lookupMethod.invoke(null,new Object[]{"javax.jnlp.BasicService"});
335 showMethod = methodTarget.getClass().getMethod("showDocument",new Class[]{URL.class});
336 } catch (ClassNotFoundException e) {
337 logger.info("Not running under java web start");
338 }
339 if (showMethod == null) {
340 try {
341 Class jdicClass = Class.forName("org.jdesktop.jdic.desktop.Desktop");
342 showMethod= jdicClass.getMethod("browse",new Class[]{URL.class});
343 } catch (ClassNotFoundException e1) {
344 logger.info("Not running with jdic libs");
345 }
346 }
347
348 if (showMethod == null) {
349 throw new ClassNotFoundException("Can't find any class that can control the system browser");
350 }
351 URL url = new URL(ACR_JNLP_URL);
352 showMethod.invoke(methodTarget,new Object[]{url});
353 }
354
355 private ACR createInternal() throws InstantiationException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
356
357 try {
358 Class buildClass = Class.forName("org.astrogrid.desktop.BuildInprocessACR");
359 Object o = buildClass.newInstance();
360
361 Method m = buildClass.getMethod("start",null);
362 m.invoke(o,null);
363
364 m = buildClass.getMethod("getACR",null);
365 return (ACR)m.invoke(o,null);
366 } catch (ClassNotFoundException e) {
367 logger.info("ACR implementation classes not available - must connect to a remote acr",e);
368 }
369 return null;
370 }
371
372 }
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408